package model.BD;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;

import model.Franja;
import model.PuntoFranja;
import model.PuntoParada;
import model.PuntoRuta;
import model.Ruta;
import model.Vehiculo;
import model.Viaje;
import model.Constants.DiasFranja;
import model.Constants.EstadosViaje;
import model.utils.RutaConverter;

public class ViajesDAO {

	public static String GET_VIAJES_STATEMENT = "select v.idviaje, v.nombre, v.salida_timestamp, r.idruta, r.nombre_ruta, r.camino, ve.idvehiculo, ve.nombre, ve.numero_vehiculo, ve.identificador_gps, g.idgrupo"
			+ " from (viajes v, rutas r, vehiculos ve, grupos g) "
			+ " where v.fkvehiculo = ve.idvehiculo and v.fkruta = r.idruta and v.fkgrupo = g.idgrupo and v.status = 0";

	public static String GET_PUNTOS_RUTA_STATEMENT = "select pr.idpunto_por_ruta, pr.latitud, pr.longitud, pr.orden, pr.segundos_anterior, pr.distancia_anterior, pr.nombre"
			+ " from puntos_por_ruta pr" + " where pr.fkruta = ?";

	public static String UPDATE_STATUS_VIAJE = "update viajes" + " set status = ? " + " where idviaje = ?";
	public static String GET_FRANJAS = "select f.idfranja, f.nombre, f.dias, f.horario_desde, f.horario_hasta" + " from franjas f "
			+ " where f.status = 1";

	public static String GET_PUNTOS_FRANJA_ACTUAL = "select tf.fkpunto_por_ruta, tf.segundos_anterior" + " from tiempo_por_franja_por_punto tf "
			+ " where tf.fkruta = ? and tf.fkfranja = ?";

	public static String UPDATE_TIMESTAMP_SALIDA_VIAJE = "update viajes" + " set salida_real_timestamp = ? " + " where idviaje = ?";

	public static String UPDATE_TIMESTAMP_LLEGADA_VIAJE = "update viajes" + " set llegada_real_timestamp = ? " + " where idviaje = ?";

	public static final int ID_VIAJE = 1;
	public static final int NOMBRE_VIAJE = 2;
	public static final int FECHA_VIAJE = 3;
	public static final int ID_RUTA = 4;
	public static final int NOMBRE_RUTA = 5;
	public static final int CAMINO_RUTA = 6;
	public static final int ID_VEHICULO = 7;
	public static final int NOMBRE_VEHICULO = 8;
	public static final int NUMERO_VEHICULO = 9;
	public static final int ID_GPS_VEHICULO = 10;
	public static final int ID_GRUPO = 11;

	public static final int ID_PUNTO_RUTA = 1;
	public static final int LATITUD_PUNTO_RUTA = 2;
	public static final int LONGITUD_PUNTO_RUTA = 3;
	public static final int ORDEN_PUNTO_RUTA = 4;
	public static final int SEGUNDOS_ANTERIOR_PUNTO_RUTA = 5;
	public static final int DISTANCIA_ANTERIOR_PUNTO_RUTA = 6;
	public static final int NOMBRE_PUNTO_RUTA = 7;

	public static final int ID_FRANJA = 1;
	public static final int NOMBRE_FRANJA = 2;
	public static final int DIAS_FRANJA = 3;
	public static final int H_DESDE_FRANJA = 4;
	public static final int H_HASTA_FRANJA = 5;

	public static final int ID_PUNTO_FRANJA_ACTUAL = 1;
	public static final int SEGUNDOS_ANTERIOR_FRANJA_ACTUAL = 2;

	private static ViajesDAO viajesDAO;

	/** Creates a new instance of ViajesDAO */
	private ViajesDAO() {

	}

	public static ViajesDAO obtenerInstancia() {
		if (viajesDAO == null) {
			viajesDAO = new ViajesDAO();
			return viajesDAO;
		} else {
			return viajesDAO;
		}
	}

	public List<Viaje> obtenerViajes() {

		List<Viaje> viajes = new ArrayList<Viaje>();
		BasedeDatos bd = BasedeDatos.obtenerInstancia();
		if (bd.getConexion() == null) {
			bd.establecerConexion();
		}

		ResultSet rs = bd.runStatement(GET_VIAJES_STATEMENT);

		if (rs != null) {
			try {
				while (rs.next()) {

					Viaje viaje = crearViaje(rs);

					viajes.add(viaje);

				}
			} catch (SQLException e) {
				System.out.println("ViajesDao.obtenerViajes(): Error obteniendo viajes: " + e.getMessage());
			} finally {
				bd.cerrar(rs);
				rs = null;
				bd.destruir();
			}
		} else {
			System.out.println("ViajesDao.obtenerViajes(): Error obteniendo viajes");
			return null;
		}

		for (Viaje viaje : viajes) {
			int idRuta = viaje.getRuta().getIdRuta();
			List<PuntoParada> puntos = obtenerParadas(idRuta);
			viaje.getRuta().setParadasPorRuta(puntos);
		}

		return viajes;
	}

	private List<PuntoParada> obtenerParadas(int idRuta) {
		List<PuntoParada> puntos = new ArrayList<PuntoParada>();

		BasedeDatos bd = BasedeDatos.obtenerInstancia();
		try {
			if (bd.getConexion() == null) {
				bd.establecerConexion();
			}
			PreparedStatement stmt = bd.getConexion().prepareStatement(GET_PUNTOS_RUTA_STATEMENT);
			stmt.setLong(1, idRuta);
			ResultSet rs = bd.runPreparedStatement(stmt);
			if (rs != null) {
				try {
					while (rs.next()) {

						PuntoParada puntoParada = crearPuntoParada(rs);

						puntos.add(puntoParada);

					}
				} catch (SQLException e) {
					System.out.println("ViajesDao.obtenerParadas(): Error obteniendo paradas de la ruta: " + e.getMessage());
				} finally {
					bd.cerrar(rs);
					bd.destruir();
				}
			} else {
				System.out.println("ViajesDao.obtenerParadas(): Error obteniendo paradas de la ruta");
				return null;
			}
		} catch (SQLException e) {
			System.out.println("ViajesDao.obtenerParadas(): ViajesDAO: Error obteniendo paradas de la ruta: " + e.getMessage());
		}

		puntos = actualizarPuntosConFranjas(puntos, idRuta);

		return puntos;
	}

	private List<PuntoParada> actualizarPuntosConFranjas(List<PuntoParada> puntos, int idRuta) {

		List<Franja> franjas = obtenerListaFranjas();
		Franja franjaActual = obtenerFranjaActual(franjas);
		if (franjaActual != null) {
			List<PuntoFranja> puntosEnFranja = obtenerPuntosEnFranja(franjaActual.getIdFranja(), idRuta);

			for (PuntoFranja puntoFranja : puntosEnFranja) {
				for (PuntoParada punto : puntos) {
					if (puntoFranja.getIdPunto() == punto.getIdPunto()) {
						punto.setSegundosAnterior(puntoFranja.getSegundosAnterior());
						break;
					}
				}
			}
		}

		return puntos;
	}

	private List<PuntoFranja> obtenerPuntosEnFranja(int idFranja, int idRuta) {

		List<PuntoFranja> puntos = new ArrayList<PuntoFranja>();

		BasedeDatos bd = BasedeDatos.obtenerInstancia();
		try {
			if (bd.getConexion() == null) {
				bd.establecerConexion();
			}
			PreparedStatement stmt = bd.getConexion().prepareStatement(GET_PUNTOS_FRANJA_ACTUAL);
			stmt.setLong(1, idRuta);
			stmt.setLong(2, idFranja);
			ResultSet rs = bd.runPreparedStatement(stmt);
			if (rs != null) {
				try {
					while (rs.next()) {

						PuntoFranja puntoFranja = crearPuntoFranja(rs);

						puntos.add(puntoFranja);

					}
				} catch (SQLException e) {
					System.out.println("ViajesDao.obtenerPuntosEnFranja(): Error obteniendo paradas de la ruta: " + e.getMessage());
				} finally {
					bd.cerrar(rs);
					bd.destruir();
				}
			} else {
				System.out.println("ViajesDao.obtenerPuntosEnFranja(): Error obteniendo paradas de la ruta");
				return null;
			}
		} catch (SQLException e) {
			System.out.println("ViajesDao.obtenerPuntosEnFranja():  Error obteniendo paradas de la ruta: " + e.getMessage());
		}

		return puntos;
	}

	private PuntoFranja crearPuntoFranja(ResultSet rs) {
		PuntoFranja puntoFranja = new PuntoFranja();
		try {
			puntoFranja.setIdPunto(rs.getInt(ID_PUNTO_FRANJA_ACTUAL));
			puntoFranja.setSegundosAnterior(rs.getInt(SEGUNDOS_ANTERIOR_FRANJA_ACTUAL));
		} catch (SQLException e) {
			System.out.println("ViajesDao.crearPuntoFranja(): Error creando franja: " + e.getMessage());
		}

		return puntoFranja;
	}

	private Franja obtenerFranjaActual(List<Franja> franjas) {

		Date ahora = new Date(System.currentTimeMillis());
		Calendar cal = Calendar.getInstance();
		cal.setTime(ahora);

		for (Franja franja : franjas) {
			if (diaDentroDeFranja(franja, cal)) {
				return franja;
			}
		}
		return null;
	}

	private boolean diaDentroDeFranja(Franja franja, Calendar cal) {
		int day = cal.get(Calendar.DAY_OF_WEEK);
		int hDesde = franja.getHorario_desde();
		int hHasta = franja.getHorario_hasta();
		switch (day) {
			case 1:
				day = DiasFranja.DOMINGO;
				break;
			case 2:
				day = DiasFranja.LUNES;
				break;
			case 3:
				day = DiasFranja.MARTES;
				break;
			case 4:
				day = DiasFranja.MIERCOLES;
				break;
			case 5:
				day = DiasFranja.JUEVES;
				break;
			case 6:
				day = DiasFranja.VIERNES;
				break;
			case 7:
				day = DiasFranja.SABADO;
				break;
			default:
				return false;
		}

		if ((franja.getDias() & day) == 0) {
			return false;
		}
		int horaActual = cal.get(Calendar.HOUR_OF_DAY) * 3600 + cal.get(Calendar.MINUTE) * 60 + cal.get(Calendar.SECOND);
		if (horaActual >= hDesde && horaActual <= hHasta) {
			return true;
		} else {
			return false;
		}
	}

	private List<Franja> obtenerListaFranjas() {

		List<Franja> franjas = new ArrayList<Franja>();
		BasedeDatos bd = BasedeDatos.obtenerInstancia();

		try {
			if (bd.getConexion() == null) {
				bd.establecerConexion();
			}
			PreparedStatement stmt = bd.getConexion().prepareStatement(GET_FRANJAS);
			ResultSet rs = bd.runPreparedStatement(stmt);
			if (rs != null) {
				try {
					while (rs.next()) {

						Franja franja = crearFranja(rs);

						franjas.add(franja);

					}
				} catch (SQLException e) {
					System.out.println("ViajesDao.obtenerListaFranjas(): Error obteniendo franjas: " + e.getMessage());
				} finally {
					bd.cerrar(rs);
					bd.destruir();
				}
			} else {
				System.out.println("ViajesDao.obtenerListaFranjas(): Error obteniendo franjas");
				return null;
			}
		} catch (SQLException e) {
			System.out.println("ViajesDao.obtenerListaFranjas(): Error obteniendo franjas: " + e.getMessage());
		}
		return franjas;
	}

	private Franja crearFranja(ResultSet rs) {
		Franja franja = new Franja();
		try {
			franja.setIdFranja(rs.getInt(ID_FRANJA));
			franja.setNombre(rs.getString(NOMBRE_FRANJA));
			franja.setDias(rs.getInt(DIAS_FRANJA));
			franja.setHorario_desde(rs.getInt(H_DESDE_FRANJA));
			franja.setHorario_hasta(rs.getInt(H_HASTA_FRANJA));
		} catch (SQLException e) {
			System.out.println("ViajesDao.crearFranja(): Error creando franja: " + e.getMessage());
		}

		return franja;
	}

	private Viaje crearViaje(ResultSet rs) {

		Viaje viaje = new Viaje();

		try {
			viaje.setIdViaje(rs.getInt(ID_VIAJE));
			viaje.setNombreViaje(rs.getString(NOMBRE_VIAJE));
			int fechaViaje = rs.getInt(FECHA_VIAJE);
			Calendar cal = Calendar.getInstance(TimeZone.getDefault());
			cal.setTimeInMillis((long) fechaViaje * 1000);
			viaje.setFechaSalida(cal.getTime());
			viaje.setRuta(crearRuta(rs));
			viaje.setVechiculo(crearVehiculo(rs));
			viaje.setIdGrupo(rs.getInt(ID_GRUPO));
		} catch (SQLException e) {
			System.out.println("ViajesDao.crearViaje(): Error creando viaje: " + e.getMessage());
		}

		return viaje;
	}

	private Vehiculo crearVehiculo(ResultSet rs) throws SQLException {
		Vehiculo vehiculo = new Vehiculo();
		vehiculo.setIdVehiculo(rs.getInt(ID_VEHICULO));
		vehiculo.setNombre(rs.getString(NOMBRE_VEHICULO));
		vehiculo.setNroVechiculo(rs.getInt(NUMERO_VEHICULO));
		vehiculo.setIdGPS(rs.getString(ID_GPS_VEHICULO));
		return vehiculo;
	}

	private Ruta crearRuta(ResultSet rs) throws SQLException {
		Ruta ruta = new Ruta();
		ruta.setIdRuta(rs.getInt(ID_RUTA));
		ruta.setNombre(rs.getString(NOMBRE_RUTA));
		RutaConverter rc = new RutaConverter();
		ruta.setTrazado(rc.convertirRuta(rs.getString(CAMINO_RUTA)));
		return ruta;
	}

	private PuntoParada crearPuntoParada(ResultSet rs) throws SQLException {
		PuntoParada puntoParada = new PuntoParada();

		puntoParada.setIdPunto(rs.getInt(ID_PUNTO_RUTA));
		puntoParada.setLatitud(rs.getDouble(LATITUD_PUNTO_RUTA));
		puntoParada.setLongitud(rs.getDouble(LONGITUD_PUNTO_RUTA));
		puntoParada.setOrden(rs.getInt(ORDEN_PUNTO_RUTA));
		puntoParada.setSegundosAnterior(rs.getInt(SEGUNDOS_ANTERIOR_PUNTO_RUTA));
		puntoParada.setDistanciaAnterior(rs.getInt(DISTANCIA_ANTERIOR_PUNTO_RUTA));
		puntoParada.setNombre(rs.getString(NOMBRE_PUNTO_RUTA));

		return puntoParada;
	}

	public void actualizarEstado(int idViaje, int estado) {
		BasedeDatos bd = BasedeDatos.obtenerInstancia();
		try {
			if (bd.getConexion() == null) {
				bd.establecerConexion();
			}
			PreparedStatement stmt = bd.getConexion().prepareStatement(UPDATE_STATUS_VIAJE);
			stmt.setInt(1, estado);
			stmt.setInt(2, idViaje);
			stmt.executeUpdate();

			System.out.println(String.format("ViajesDao.actualizarEstado(): Estado del viaje: %d actualizado a: %s", idViaje, EstadosViaje.getEstadoPorId(estado)));
		} catch (SQLException e) {
			System.out.println(String.format("ViajesDao.actualizarEstado(): ViajesDAO: Error actualizando el estado del viaje: %d - Error: %s", idViaje, e.getMessage()));
		} finally {
			bd.destruir();
		}
	}

	public void actualizarTimestampSalida(int idViaje, long timestamp) {
		BasedeDatos bd = BasedeDatos.obtenerInstancia();
		try {
			if (bd.getConexion() == null) {
				bd.establecerConexion();
			}
			PreparedStatement stmt = bd.getConexion().prepareStatement(UPDATE_TIMESTAMP_SALIDA_VIAJE);
			stmt.setInt(1, (int) (timestamp / 1000));
			stmt.setInt(2, idViaje);
			stmt.executeUpdate();

			System.out.println(String.format("ViajesDao.actualizarTimestampSalida(): viaje: %d timestamp salida real: %s", idViaje, timestamp / 1000));
		} catch (SQLException e) {
			System.out.println(String.format("ViajesDao.actualizarTimestampSalida(): Error actualizando el timestamp salida real del viaje: %d - Error: %s", idViaje, e.getMessage()));
		} finally {
			bd.destruir();
		}
	}

	public void actualizarTimestampLlegada(int idViaje, long timestamp) {
		BasedeDatos bd = BasedeDatos.obtenerInstancia();
		try {
			if (bd.getConexion() == null) {
				bd.establecerConexion();
			}
			PreparedStatement stmt = bd.getConexion().prepareStatement(UPDATE_TIMESTAMP_LLEGADA_VIAJE);
			stmt.setInt(1, (int) (timestamp / 1000));
			stmt.setInt(2, idViaje);
			stmt.executeUpdate();

			System.out.println(String.format("ViajesDao.actualizarTimestampLlegada(): viaje: %d timestamp llegada real: %s", idViaje, timestamp / 1000));
		} catch (SQLException e) {
			System.out.println(String.format("ViajesDao.actualizarTimestampLlegada(): Error actualizando el timestamp llegada real del viaje: %d - Error: %s", idViaje, e.getMessage()));
		} finally {
			bd.destruir();
		}
	}
}
